package com.kandinfo.base.common.repository;

import java.util.Arrays;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.property.ChainedPropertyAccessor;
import org.hibernate.property.DirectPropertyAccessor;
import org.hibernate.property.EmbeddedPropertyAccessor;
import org.hibernate.property.MapAccessor;
import org.hibernate.property.NoopAccessor;
import org.hibernate.property.PropertyAccessor;
import org.hibernate.property.Setter;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("rawtypes")
public class AliasToBeanResultTransformerEx extends AliasedTupleSubsetResultTransformer{

	private static final Logger LOGGER = LoggerFactory.getLogger(AliasToBeanResultTransformerEx.class);
	
	private static final long serialVersionUID = 1L;

 	
	private String getProp(String alias){
		StringBuffer name = new StringBuffer();
		int i = 0;
		for(String s : alias.split("_")){
			if(i == 0){
				name.append(s);
			}else{
				name.append(StringUtils.capitalize(s));
			}
			 i++;
		}
		return name.toString();
	}
	
	
	private final Class resultClass;
	private boolean isInitialized;
	private String[] aliases;
	private Setter[] setters;

	public AliasToBeanResultTransformerEx(Class resultClass) {
		if ( resultClass == null ) {
			throw new IllegalArgumentException( "resultClass cannot be null" );
		}
		isInitialized = false;
		this.resultClass = resultClass;
	}

	@Override
	public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
		return false;
	}
	
	@Override
	public Object transformTuple(Object[] tuple, String[] aliases) {
		Object result;

		try {
			if ( ! isInitialized ) {
				String[] newAliases = new String[aliases.length];
				for(int i = 0; i< aliases.length;i++){
					newAliases[i] = getProp(aliases[i]);
				}
				initialize( newAliases );
			}
			else {
				check( aliases );
			}
			
			result = resultClass.newInstance();

			for ( int i = 0; i < aliases.length; i++ ) {
				if ( setters[i] != null ) {
					setters[i].set( result, tuple[i], null );
				}
			}
		}
		catch ( InstantiationException e ) {
			LOGGER.debug("", e);
			throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
		}
		catch ( IllegalAccessException e ) {
			LOGGER.debug("", e);
			throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
		}

		return result;
	}

	private void initialize(String[] aliases) {
		PropertyAccessor propertyAccessor = new ChainedPropertyAccessor(
				new PropertyAccessor[] {
						getPropertyAccessor( resultClass,null ),
						getPropertyAccessor( "field" )
				}
		);
		this.aliases = new String[ aliases.length ];
		setters = new Setter[ aliases.length ];
		for ( int i = 0; i < aliases.length; i++ ) {
			String alias = aliases[ i ];
			if ( alias != null ) {
				this.aliases[ i ] = alias;
				setters[ i ] = propertyAccessor.getSetter( resultClass, alias );
			}
		}
		isInitialized = true;
	}

	private void check(String[] aliases) {
		if ( ! Arrays.equals( aliases, this.aliases ) ) {
			throw new IllegalStateException(
					"aliases are different from what is cached; aliases=" + Arrays.asList( aliases ) +
							" cached=" + Arrays.asList( this.aliases ) );
		}
	}

	@Override
	public boolean equals(Object o) {
		if ( this == o ) {
			return true;
		}
		if ( o == null || getClass() != o.getClass() ) {
			return false;
		} 

		AliasToBeanResultTransformerEx that = ( AliasToBeanResultTransformerEx ) o;

		if ( ! resultClass.equals( that.resultClass ) ) {
			return false;
		}
		if ( ! Arrays.equals( aliases, that.aliases ) ) {
			return false;
		}

		return true;
	}
	private static final PropertyAccessor BASIC_PROPERTY_ACCESSOR = new BasicPropertyAccessorEx();
	private static final PropertyAccessor DIRECT_PROPERTY_ACCESSOR = new DirectPropertyAccessor();
	private static final PropertyAccessor MAP_ACCESSOR = new MapAccessor();
	private static final PropertyAccessor NOOP_ACCESSOR = new NoopAccessor();
	private static final PropertyAccessor EMBEDDED_PROPERTY_ACCESSOR = new EmbeddedPropertyAccessor();
	
	private static PropertyAccessor resolveCustomAccessor(String accessorName) {
		Class accessorClass;
		try {
			accessorClass = ReflectHelper.classForName( accessorName );
		}
		catch (ClassNotFoundException cnfe) {
			throw new MappingException("could not find PropertyAccessor class: " + accessorName, cnfe);
		}
		try {
			return (PropertyAccessor) accessorClass.newInstance();
		}
		catch (Exception e) {
			throw new MappingException("could not instantiate PropertyAccessor class: " + accessorName, e);
		}
	}
	public static PropertyAccessor getPropertyAccessor(Class optionalClass, String type) throws MappingException {
		if ( type==null ) type = optionalClass==null || optionalClass==Map.class ? "map" : "property";
		return getPropertyAccessor(type);
	}
	public static PropertyAccessor getPropertyAccessor(String type) throws MappingException {
		if ( type==null || "property".equals(type) ) return BASIC_PROPERTY_ACCESSOR;
		if ( "field".equals(type) ) return DIRECT_PROPERTY_ACCESSOR;
		if ( "map".equals(type) ) return MAP_ACCESSOR;
		if ( "embedded".equals(type) ) return EMBEDDED_PROPERTY_ACCESSOR;
		if ( "noop".equals(type)) return NOOP_ACCESSOR;

		return resolveCustomAccessor(type);
	}
	@Override
	public int hashCode() {
		int result = resultClass.hashCode();
		result = 31 * result + ( aliases != null ? Arrays.hashCode( aliases ) : 0 );
		return result;
	}
}
